<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MessagePacket WebSocket Client</title>
    <script src="https://cdn.jsdelivr.net/npm/protobufjs@7.2.4/dist/protobuf.min.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
            max-width: 800px;
            margin: 0 auto;
        }
        .container {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        .log-container {
            border: 1px solid #ccc;
            padding: 10px;
            height: 300px;
            overflow-y: auto;
            background-color: #f9f9f9;
            font-family: monospace;
        }
        .control-panel {
            display: flex;
            flex-direction: column;
            gap: 10px;
            margin-bottom: 20px;
        }
        textarea {
            width: 100%;
            height: 100px;
            resize: vertical;
        }
        button {
            padding: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>MessagePacket WebSocket Client</h1>
        
        <div class="control-panel">
            <h2>Connection</h2>
            <div>
                <input type="text" id="serverUrl" value="ws://localhost:8080" style="width: 300px;">
                <button id="connectBtn">Connect</button>
                <button id="disconnectBtn" disabled>Disconnect</button>
            </div>
        </div>
        
        <div class="control-panel">
            <h2>Send Message</h2>
            <div>
                <input type="text" id="msgId" placeholder="Message ID" style="width: 250px;">
            </div>
            <div>
                <textarea id="msgBody" placeholder="Message Body"></textarea>
            </div>
            <div>
                <textarea id="msgProps" placeholder="Properties (JSON format, e.g. {'key1': 'value1', 'key2': 'value2'})">{"client": "web"}</textarea>
            </div>
            <div>
                <label>
                    <input type="checkbox" id="compressMsg"> Compress
                </label>
            </div>
            <button id="sendBtn" disabled>Send Message</button>
        </div>
        
        <h2>Logs</h2>
        <div class="log-container" id="logs"></div>
    </div>

    <script>
        // Load Protocol Buffers definitions
        const protocDef = `
            syntax = "proto3";
            
            message Packet {
                enum PacketType {
                    RpcIssue = 0;
                    Request = 1;
                    Response = 2;
                }
                
                PacketType packetType = 1;
                string method = 2;
                bytes context = 3;
                
                enum CompressType {
                    None = 0;
                    Gzip = 1;
                }
                
                CompressType compressType = 4;
                RequestPacket request = 5;
                ResponsePacket response = 6;
            }
            
            message RequestPacket {
                int32 seqId = 1;
                bool inline = 2;
                int64 timeout = 20;
            }
            
            message ResponsePacket {
                int32 seqId = 1;
                string error = 3;
            }
            
            message MessagePacket {
                string msgid = 1;
                map<string, string> props = 2;
                bytes body = 3;
                bool compress = 4;
                string error = 5;
                int32 level = 6;
            }
        `;
        
        // DOM Elements
        const serverUrlInput = document.getElementById('serverUrl');
        const connectBtn = document.getElementById('connectBtn');
        const disconnectBtn = document.getElementById('disconnectBtn');
        const msgIdInput = document.getElementById('msgId');
        const msgBodyTextarea = document.getElementById('msgBody');
        const msgPropsTextarea = document.getElementById('msgProps');
        const compressMsgCheckbox = document.getElementById('compressMsg');
        const sendBtn = document.getElementById('sendBtn');
        const logsDiv = document.getElementById('logs');
        
        // WebSocket and Protocol Buffers
        let ws = null;
        let root = null;
        let Packet = null;
        let MessagePacket = null;
        let seqId = 1;
        
        // Initialize Protocol Buffers
        protobuf.parse(protocDef, { keepCase: true }).then((parsed) => {
            root = parsed.root;
            Packet = root.lookupType("Packet");
            MessagePacket = root.lookupType("MessagePacket");
            log("Protocol Buffers definitions loaded");
        }).catch(err => {
            logError("Failed to parse protobuf: " + err);
        });
        
        // Logging functions
        function log(message) {
            const timestamp = new Date().toTimeString().split(' ')[0];
            logsDiv.innerHTML += `<div>[${timestamp}] ${message}</div>`;
            logsDiv.scrollTop = logsDiv.scrollHeight;
        }
        
        function logError(message) {
            const timestamp = new Date().toTimeString().split(' ')[0];
            logsDiv.innerHTML += `<div style="color: red">[${timestamp}] ERROR: ${message}</div>`;
            logsDiv.scrollTop = logsDiv.scrollHeight;
        }
        
        // WebSocket functions
        function connect() {
            const url = serverUrlInput.value;
            if (!url) {
                logError("Server URL is required");
                return;
            }
            
            try {
                ws = new WebSocket(url);
                
                ws.onopen = () => {
                    log(`Connected to ${url}`);
                    connectBtn.disabled = true;
                    disconnectBtn.disabled = false;
                    sendBtn.disabled = false;
                };
                
                ws.onmessage = (event) => {
                    handleMessage(event.data);
                };
                
                ws.onclose = (event) => {
                    log(`Disconnected: Code ${event.code} - ${event.reason || 'No reason provided'}`);
                    connectBtn.disabled = false;
                    disconnectBtn.disabled = true;
                    sendBtn.disabled = true;
                    ws = null;
                };
                
                ws.onerror = (error) => {
                    logError(`WebSocket Error: ${error.message || 'No details available'}`);
                };
            } catch (error) {
                logError(`Failed to connect: ${error.message}`);
            }
        }
        
        function disconnect() {
            if (ws) {
                ws.close();
            }
        }
        
        // Handle received messages
        async function handleMessage(data) {
            try {
                const buffer = await data.arrayBuffer();
                const uint8Array = new Uint8Array(buffer);
                
                // Decode the packet
                const decodedPacket = Packet.decode(uint8Array);
                const packet = Packet.toObject(decodedPacket, {
                    longs: String,
                    enums: String,
                    bytes: String,
                });
                
                log(`Received message - Type: ${packet.packetType}, Method: ${packet.method}`);
                
                // If it's a notification, try to decode the context as MessagePacket
                if (packet.packetType === "RpcIssue") {
                    try {
                        const contextBuffer = decodedPacket.context;
                        const decodedMessage = MessagePacket.decode(contextBuffer);
                        const message = MessagePacket.toObject(decodedMessage, {
                            longs: String,
                            enums: String,
                            bytes: String,
                        });
                        
                        log(`Received MessagePacket - ID: ${message.msgid}`);
                        log(`Message Body: ${message.body}`);
                        log(`Properties: ${JSON.stringify(message.props)}`);
                        
                        if (message.error) {
                            logError(`Message Error: ${message.error}`);
                        }
                    } catch (err) {
                        log(`Cannot decode context as MessagePacket: ${err.message}`);
                    }
                }
            } catch (error) {
                logError(`Failed to parse message: ${error.message}`);
            }
        }
        
        // Send a message
        function sendMessage() {
            if (!ws || ws.readyState !== WebSocket.OPEN) {
                logError("Not connected to server");
                return;
            }
            
            const msgId = msgIdInput.value;
            if (!msgId) {
                logError("Message ID is required");
                return;
            }
            
            const msgBody = msgBodyTextarea.value;
            
            // Parse message properties
            let props = {};
            try {
                if (msgPropsTextarea.value) {
                    props = JSON.parse(msgPropsTextarea.value.replace(/'/g, '"'));
                }
            } catch (error) {
                logError(`Invalid properties format: ${error.message}`);
                return;
            }
            
            // Create MessagePacket
            const messagePacket = MessagePacket.create({
                msgid: msgId,
                body: stringToUtf8Array(msgBody),
                props: props,
                compress: compressMsgCheckbox.checked,
                level: 0,
            });
            
            // Encode MessagePacket
            const messagePacketBuffer = MessagePacket.encode(messagePacket).finish();
            
            // Create Packet
            const packet = Packet.create({
                packetType: "RpcIssue",
                method: msgId,
                context: messagePacketBuffer,
                compressType: "None",
            });
            
            // Encode Packet
            const packetBuffer = Packet.encode(packet).finish();
            
            // Send the packet
            ws.send(packetBuffer);
            log(`Sent message: ${msgId}`);
        }
        
        // Utility: Convert string to UTF-8 array
        function stringToUtf8Array(str) {
            const encoder = new TextEncoder();
            return encoder.encode(str);
        }
        
        // Event Listeners
        connectBtn.addEventListener('click', connect);
        disconnectBtn.addEventListener('click', disconnect);
        sendBtn.addEventListener('click', sendMessage);
    </script>
</body>
</html> 